﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace NMemcached.Client.Text
{
    /// <summary>
    /// A memory stream used for reading memcached text responses
    /// </summary>
    internal sealed class TextStream : MemoryStream
    {
        private Encoding _encoding = Encoding.UTF8;

        /// <summary>
        /// Gets or sets the encoding for the stream
        /// </summary>
        public Encoding Encoding { get { return _encoding; } set { _encoding = value; } }

        /// <summary>
        /// Gets a value that indicates whether the current stream position is at the end of the stream
        /// </summary>
        public bool EndOfStream
        {
            get
            {
                return (this.Position >= this.Length);
            }
        }

        /// <summary>
        /// Initializes a new instance of the TextStream class
        /// </summary>
        public TextStream()
            : base()
        {
        }

        /// <summary>
        /// Initializes a new instance of the TextStream class
        /// </summary>
        /// <param name="buffer">An array of unsigned bytes from which to create the stream</param>
        public TextStream(byte[] buffer)
            : base(buffer)
        {
        }

        /// <summary>
        /// Initializes a new instance of the TextStream class
        /// </summary>
        /// <param name="capacity">The initial size of the internal array in bytes</param>
        public TextStream(int capacity)
            : base(capacity)
        {
        }

        /// <summary>
        /// Initializes a new instance of the TextStream class
        /// </summary>
        /// <param name="buffer">An array of unsigned bytes from which to create the stream</param>
        /// <param name="writable">Determines whether the stream supports writing</param>
        public TextStream(byte[] buffer, bool writable)
            : base(buffer, writable)
        {
        }

        /// <summary>
        /// Initializes a new instance of the TextStream class
        /// </summary>
        /// <param name="buffer">An array of unsigned bytes from which to create the stream</param>
        /// <param name="index">The index into buffer at which the stream begins</param>
        /// <param name="count">The length of the stream in bytes</param>
        public TextStream(byte[] buffer, int index, int count)
            : base(buffer, index, count)
        {
        }

        /// <summary>
        /// Initializes a new instance of the TextStream class
        /// </summary>
        /// <param name="buffer">An array of unsigned bytes from which to create the stream</param>
        /// <param name="index">The index into buffer at which the stream begins</param>
        /// <param name="count">The length of the stream in bytes</param>
        /// <param name="writable">Determines whether the stream supports writing</param>
        public TextStream(byte[] buffer, int index, int count, bool writable)
            : base(buffer, index, count, writable)
        {
        }

        /// <summary>
        /// Initializes a new instance of the TextStream class
        /// </summary>
        /// <param name="buffer">An array of unsigned bytes from which to create the stream</param>
        /// <param name="index">The index into buffer at which the stream begins</param>
        /// <param name="count">The length of the stream in bytes</param>
        /// <param name="writable">Determines whether the stream supports writing</param>
        /// <param name="publiclyVisible">True to enable GetBuffer(), which returns the byte array from which the stream was created; otherwise, false</param>
        public TextStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)
            : base(buffer, index, count, writable, publiclyVisible)
        {
        }

        /// <summary>
        /// Advances the stream pointer beyond the next end of line marker.
        /// </summary>
        public void ClearEndOfLine()
        {
            this.ReadLine();
        }

        /// <summary>
        /// Reads a block of bytes from the current stream and writes the data to a buffer
        /// </summary>
        /// <param name="buffer">The buffer to which the data is written</param>
        public void Read(byte[] buffer)
        {
            this.Read(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Reads all data up to the next end of line.
        /// </summary>
        /// <returns>All data from the stream up to the next end of line.</returns>
        public string ReadLine()
        {
            return this.ReadLine(this._encoding);
        }

        /// <summary>
        /// Reads all data up to the next end of line
        /// </summary>
        /// <param name="encoding">The encoding of the text data</param>
        /// <returns>All data from the stream up to the next end of line</returns>
        public string ReadLine(Encoding encoding)
        {
            List<byte> output = new List<byte>();

            byte[] buffer = new byte[1];
            bool eol = false;
            while (this.Read(buffer, 0, buffer.Length) > 0)
            {
                if (buffer[0] == 13)
                {
                    eol = true;
                }
                else
                {
                    if (eol)
                    {
                        if (buffer[0] == 10)
                        {
                            break;
                        }
                        else
                        {
                            eol = false;
                        }
                    }
                }
                output.Add(buffer[0]);
            }
            return encoding.GetString(output.ToArray()).TrimEnd('\0', '\r', '\n');
        }

        /// <summary>
        /// Reads all data up to the next end of line as bytes
        /// </summary>
        /// <returns>All data up to the next end of line as bytes</returns>
        public byte[] ReadLineAsBytes()
        {
            List<byte> output = new List<byte>();

            byte[] buffer = new byte[1];
            bool eol = false;
            while (this.Read(buffer, 0, buffer.Length) > 0)
            {
                if (buffer[0] == 13)
                {
                    eol = true;
                }
                else
                {
                    if (eol)
                    {
                        if (buffer[0] == 10)
                        {
                            break;
                        }
                        else
                        {
                            eol = false;
                        }
                    }
                }
                output.Add(buffer[0]);
            }
            //Remove the last item, which is the \r
            output.RemoveAt(output.Count - 1);
            return output.ToArray();
        }

        /// <summary>
        /// Writes the contents of a buffer to the stream
        /// </summary>
        /// <param name="buffer">The data to be written to the stream</param>
        public void Write(byte[] buffer)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("buffer");
            }
            this.Write(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Writes a string to the stream
        /// </summary>
        /// <param name="data">The string to write to the stream</param>
        public void Write(string data)
        {
            this.Write(data, this._encoding);
        }

        /// <summary>
        /// Writes a string to the stream
        /// </summary>
        /// <param name="data">The string to write to the stream</param>
        /// <param name="encoding">The encoding of the string to write</param>
        public void Write(string data, Encoding encoding)
        {
            if (String.IsNullOrEmpty(data))
            {
                throw new ArgumentNullException("data");
            }
            this.Write(encoding.GetBytes(data));
        }

        /// <summary>
        /// Writes a string to the stream and terminates it with a new line
        /// </summary>
        /// <param name="data">The string to write to the stream</param>
        public void WriteLine(string data)
        {
            this.WriteLine(data, this._encoding);
        }

        /// <summary>
        /// Writes a string to the stream and terminates it with a new line
        /// </summary>
        /// <param name="data">The string to write to the stream</param>
        /// <param name="encoding">The encoding of the string to write</param>
        public void WriteLine(string data, Encoding encoding)
        {
            if (String.IsNullOrEmpty(data))
            {
                throw new ArgumentNullException("data");
            }
            this.Write(String.Concat(data, "\r\n"), encoding);
        }
    }
}
